package com.cauli.utils.util;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.cauli.utils.exception.MyException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.DigestUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

/**
 * @author Cauli
 * @date 2023/2/24 11:00
 * @description 文件上传工具类
 */
public class FileUploadUtils {

    /**
     * 默认大小 50M
     */
    public static final long MAX_FILE_NAME_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 255
     */
    public static final int MAX_FILE_NAME_LENGTH = 255;

    /**
     * 允许上传和下载的文件类型
     */
    public static final String[] ALLOWED_EXTENSIONS = {
            // 图片
            "bmp", "gif", "jpg", "jpeg", "png",
            // word excel powerpoint
            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
            // 压缩文件
            "rar", "zip", "gz", "bz2",
            // 视频格式
            "mp4", "avi", "rmvb",
            // pdf
            "pdf"};


    /**
     * 根据文件路径上传
     *
     * @param subDir 相对应用的基目录
     * @param file   上传的文件
     * @return 文件名称
     */
    public static String upload(String subDir, MultipartFile file) {
        try {
            // 检测文件是否可以上传
            isAllowedUpload(file, ALLOWED_EXTENSIONS);
            // 编码文件名
            String fileName = generateFilename(file);
            // 将文件保存到服务器
            saveFileToLocal(file, subDir, fileName);
            // 获取文件的相对地址
            return getRelativeFileUrl(subDir, fileName);
        } catch (Exception e) {
            throw new MyException("文件上传失败");
        }
    }

    /**
     * 检测文件是否可以上传
     *
     * @param file             文件
     * @param allowedExtension 允许的文件类型列表
     */
    static void isAllowedUpload(MultipartFile file, String[] allowedExtension) {
        int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNameLength > MAX_FILE_NAME_LENGTH) {
            throw new MyException("文件名长度超过" + MAX_FILE_NAME_LENGTH);
        }

        if (file.getSize() > MAX_FILE_NAME_SIZE) {
            throw new MyException("文件名大小超过" + MAX_FILE_NAME_SIZE / 1024 * 1024);
        }

        if (!isExtensionAllowed(getFileExtension(file), allowedExtension)) {
            throw new MyException("不允许上传的文件类型");
        }
    }

    /**
     * 编码文件名
     */
    static String generateFilename(MultipartFile file) throws IOException {
        String md5 = DigestUtils.md5DigestAsHex(file.getInputStream());
        String fileName = FileNameUtil.getName(file.getOriginalFilename());
        // 获得后缀名
        String extension = FileNameUtil.getSuffix(file.getOriginalFilename());
        if (StrUtil.isEmpty(extension)) {
            // 后缀名为空，则手动添加后缀名
            MimeType mimeType = MimeTypeUtils.parseMimeType(Objects.requireNonNull(file.getContentType()));
            extension = mimeType.getSubtype();
            fileName = fileName + "." + extension;
        }
        return StrUtil.format("{}_{}", md5, fileName);
    }

    /**
     * 将文件保存到服务器
     *
     * @param file     文件
     * @param subDir   子目录
     * @param fileName 文件名
     */
    static void saveFileToLocal(MultipartFile file, String subDir, String fileName) throws IOException {
        if (StrUtil.isEmpty(subDir) || StrUtil.isEmpty(fileName)) {
            throw new MyException("参数异常");
        }
        String date = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN);
        String name = "upload" + File.separator + date + File.separator + subDir + File.separator + fileName;

        // 获得本地路径
        String classpath = Objects.requireNonNull(FileUploadUtils.class.getResource("/")).getPath();
        // 将文件转化为绝对路径
        File destination = new File(classpath + "static/" + name);
        // 父路径不存在则创建
        if (!destination.exists()) {
            if (!destination.getParentFile().exists()) {
                destination.getParentFile().mkdirs();
            }
        }
        // 保存在本地
        file.transferTo(destination);
    }

    /**
     * 获取文件的相对地址
     *
     * @param subDir   avatar
     * @param fileName test.jpg
     * @return
     */
    static String getRelativeFileUrl(String subDir, String fileName) {
        String name = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN) + "_" + fileName;
        return StrUtil.format("{}/{}", "http://localhost:8000/manage/user/profile/getFileUrl", name);
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    static String getFileExtension(MultipartFile file) {
        String extension = FileNameUtil.getSuffix(file.getOriginalFilename());
        if (StrUtil.isEmpty(extension)) {
            MimeType mimeType = MimeTypeUtils.parseMimeType(Objects.requireNonNull(file.getContentType()));
            extension = mimeType.getSubtype();
        }
        return extension;
    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     */
    static boolean isExtensionAllowed(String extension, String[] allowedExtension) {
        if (allowedExtension == null || allowedExtension.length == 0) {
            return true;
        }
        return StrUtil.containsAnyIgnoreCase(extension, allowedExtension);
    }

    /**
     * 检查文件是否可下载
     *
     * @param resource 需要下载的文件
     * @return true 正常 false 非法
     */
    public static boolean isAllowDownload(String resource) {
        // 禁止目录上跳级别
        return !StrUtil.contains(resource, "..") &&
                // 检查允许下载的文件规则
                StrUtil.containsAnyIgnoreCase(FileNameUtil.getSuffix(resource), ALLOWED_EXTENSIONS);
    }

    /**
     * 下载文件名重新编码
     *
     * @param fileName 真实文件名
     */
    public static HttpHeaders getDownloadHeader(String fileName) {
        String randomFileName = System.currentTimeMillis() + "_" + fileName;
        String fileNameUrlEncoded = URLUtil.encode(randomFileName, CharsetUtil.CHARSET_UTF_8);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Disposition", String.format("attachment;filename=%s", fileNameUrlEncoded));
        return headers;
    }
}
